<template>
  <div class="app-container calendar-list-container">
    <div class="filter-container">
      <el-select v-model="listQuery.clusterName" clearable filterable class="filter-item" placeholder="请选择集群" @change="getDeployDiagram">
        <el-option v-for="(item, index) in clusterOptions" :key="index" :label="item" :value="item" />
      </el-select>
    </div>
    <div id="topoComponent">
      <el-row class="topoRow">
        <el-col :xs="5" :sm="6" :md="6" :lg="4" :xl="6" class="topoCol">
          <div id="toolbar">
            <!-- <div class="toolbar-head">{{listQuery.clusterName}} Node Types</div> -->
            <div v-if="toolbarNodeList.length > 0" class="toolbar-main">
              <el-row :gutter="5">
                <el-col v-for="(ele, key) in toolbarNodeList" :key="ele.id">
                  <li :title="ele.type" class="node-item node-css" @mousedown.stop.prevent="dragToolbarNode(toolbarNodeList, key, $event)">
                    <div class="node-icon">
                      <img :src="ele.icon" class="toolbar-node-icon">
                    </div>
                    <div class="node-name">{{ ele.hostName }}</div>
                  </li>
                </el-col>
                <div v-if="toolbarNodeList.length === 0" class="no-list">
                  暂无数据
                </div>
              </el-row>
            </div>
          </div>
        </el-col>
        <el-col :xs="19" :sm="18" :md="18" :lg="20" :xl="18" class="svgCol">
          <div id="svgWrap">

            <!-- 拓扑工具栏 Start -->
            <div id="svgHead" class="clearfix">
              <ul class="svgHeadItemLst clearfix" style="float:left">
                <li v-for="(ele, key) in svgToolbar" :key="key" :class="{'active':ele.isActive}" :title="ele.name" class="svgHeadItem" @mousedown="selectToolbar(key)">
                  <div :class="ele.className" class="svgHeadItemImg"/>
                </li>
              </ul>
              <ul style="float:right;padding-right:10px;" class="clearfix">
                <el-button type="primary" class="filter-item" @click="handleSave"><i class="fa fa-save"/> {{ $t('common.save') }}</el-button>
                <el-button type="success" class="filter-item" @click="saveTopoImage"><i class="fa fa-file-image-o"/> 保存图片</el-button>
              </ul>
            </div>
            <!-- 拓扑工具栏 End -->

            <!-- 拓扑流程图 Start -->
            <div id="topo-wrap" style="position:relative">
              <svg
                id="topo-svg"
                :width="svgAttr.width"
                :height="svgAttr.height"
                :viewBox="svgAttr.viewX+' '+svgAttr.viewY+' '+svgAttr.width+' '+svgAttr.height"
                :class="{'hand':svgAttr.isHand,'crosshair':svgAttr.isCrosshair}"
                @mousedown.stop="mousedownTopoSvg($event)">
                <defs>
                  <pattern id="Pattern" x="0" y="0" width="100" height="100" patternUnits="userSpaceOnUse">
                    <line v-for="ele in gridData" :key="ele.id" :x1="ele.x1" :x2="ele.x2" :y1="ele.y1" :y2="ele.y2" :stroke="ele.color" :stroke-width="ele.strokeWidth" :opacity="ele.opacity"/>
                  </pattern>
                </defs>
                <rect :width="svgAttr.width" :height="svgAttr.height" fill="url(#Pattern)" />
                <g>
                  <g
                    v-for="(ele, key) in topoData.nodes"
                    :transform="'translate('+ele.x+', '+ele.y+')'"
                    :key="ele.id"
                    class="nodesG"
                    @mouseover.stop="mouseoverNode(key, $event)"
                    @mousedown.stop="dragSvgNode(key, $event)"
                    @mouseout.stop="mouseoutLeftConnector(key)">
                    <rect :width="ele.width" :height="ele.height" :class="{isSelect:ele.isSelect}" x="0" y="0" rx="2" ry="2" class="reactClass" />
                    <text v-if="ele.classType === 'T1'" class="nodeName" x="24" y="18">{{ ele.hostName }}</text>
                    <image v-if="ele.classType === 'T1'" :xlink:href="ele.icon" :x="4" :y="6" class="nodeImg" height="15px" width="15px"/>
                    <image v-if="ele.classType === 'T2'" :xlink:href="ele.icon" :x="7" :y="7" class="nodeImg" height="36px" width="36px"/>
                    <text v-if="ele.classType === 'T2'" :y="ele.height + 14" x="0" class="nodeName" >{{ ele.hostName }}</text>
                    <g :class="{'connector':ele.isLeftConnectShow}" :transform="'translate(0, '+ele.height/2+')'" class="connectorArror">
                      <circle r="8" cx="0" cy="0" class="circleColor"/>
                      <line x1="-3" y1="-5" x2="4" y2="0.5" stroke="#fff"/>
                      <line x1="4" y1="-0.5" x2="-3" y2="5" stroke="#fff"/>
                    </g>
                    <g :class="{'connector':ele.isRightConnectShow}" :transform="'translate('+ele.width+', '+ele.height/2+')'" class="connectorArror" @mousedown.stop="drawConnectLine(key, $event)">
                      <circle r="8" cx="0" cy="0" class="circleColor"/>
                      <line x1="-3" y1="-5" x2="4" y2="0.5" stroke="#fff"/>
                      <line x1="4" y1="-0.5" x2="-3" y2="5" stroke="#fff"/>
                    </g>
                  <!-- <g class="removeEle" :transform="'translate('+ele.width+', 0)'" @mousedown.stop="deleteNodeAndConnetor($event)">
                    <circle r="10" cx="0" cy="0" class="circleCancel"></circle>
                    <line x1="-4" y1="-5" x2="5" y2="4.5" stroke="#fff"></line>
                    <line x1="6" y1="-5" x2="-4.5" y2="4" stroke="#fff"></line>
                  </g>
                  <div class="removeEle">2222</div> -->
                  </g>

                  <!-- node间关系连线样式 -->
                  <g
                    v-for="(ele, key) in topoData.connectors"
                    v-if="ele.type === 'Line'"
                    :class="{active:ele.isSelect}"
                    :key="key"
                    class="connectorsG"
                    @mousedown.stop="selectConnectorLine(key)">
                    <!-- 自连 -->
                    <path
                      v-if="ele.sourceNode.id === ele.targetNode.id"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2)+
                        'h'+connectorWSelf+
                        'v'+(-(ele.sourceNode.height / 2 + connectorWSelf))+
                        'h'+ (-(ele.sourceNode.width + 2 * connectorWSelf)) +
                        'v'+(ele.sourceNode.height / 2 + connectorWSelf) +
                      'H' + (ele.targetNode.x)"
                      class="connectorLine"
                    />
                    <!-- 非自连: 1.sourceNode 的右侧箭头X <= targetNode的左侧箭头X -->
                    <path
                      v-if="ele.sourceNode.id != ele.targetNode.id &&
                      (ele.sourceNode.x +ele.sourceNode.width) < ele.targetNode.x"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2) +
                        'h'+ (ele.targetNode.x - ele.sourceNode.x - ele.sourceNode.width) / 2 +
                        'V' + (ele.targetNode.y + ele.targetNode.height / 2) +
                      'H' + ele.targetNode.x"
                      class="connectorLine"
                    />
                    <!-- 非自连：2.sourceNode 的右侧箭头X >= targetNode的左侧箭头X
                  (1) 且 sourceNode的高度 < targetNode的高度 且 高度未重叠 -->
                    <path
                      v-if="ele.sourceNode.id != ele.targetNode.id &&
                        (ele.sourceNode.x + ele.sourceNode.width) >= ele.targetNode.x &&
                      (ele.sourceNode.y + ele.sourceNode.height ) < ele.targetNode.y"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2) +
                        'h'+connectorWSelf+
                        'v'+(ele.sourceNode.height / 2 + (ele.targetNode.y - ele.sourceNode.y - ele.sourceNode.height) / 2) +
                        'H'+(ele.targetNode.x - connectorWSelf) +
                        'V'+(ele.targetNode.y + ele.targetNode.height / 2) +
                      'h'+connectorWSelf"
                      class="connectorLine"
                    />
                    <!-- 非自连：2.sourceNode 的右侧箭头X >= targetNode的左侧箭头X
                    (2) 且 sourceNode的高度 > targetNode的高度 且 高度未重叠 -->
                    <path
                      v-if="ele.sourceNode.id != ele.targetNode.id &&
                        (ele.sourceNode.x + ele.sourceNode.width) >= ele.targetNode.x &&
                      (ele.targetNode.y + ele.targetNode.height) < ele.sourceNode.y"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2) +
                        'h'+connectorWSelf+
                        'V'+(ele.sourceNode.y-(ele.sourceNode.y - ele.targetNode.y - ele.targetNode.height) / 2) +
                        'H'+ (ele.targetNode.x - connectorWSelf) +
                        'V'+(ele.targetNode.y + ele.targetNode.height / 2) +
                      'H'+ele.targetNode.x"
                      class="connectorLine"
                    />
                    <!-- 非自连：
                   2.sourceNode 的右侧箭头X >= targetNode的左侧箭头X
                   (3) sourceNode的箭头y < = targetNode的箭头
                  sourceNode 的y < targetNode的y < = (sourceNode 的y + sourceNode的height) 或者 sourceNode的y介于其间
                   高度重叠-->
                    <path
                      v-if="ele.sourceNode.id != ele.targetNode.id &&
                        (ele.sourceNode.x + ele.sourceNode.width) >= ele.targetNode.x &&
                        (ele.sourceNode.y + ele.sourceNode.height/2) <= (ele.targetNode.y + ele.targetNode.height / 2) &&
                        ((ele.targetNode.y <= (ele.sourceNode.y + ele.sourceNode.height) && ele.targetNode.y >= ele.sourceNode.y) ||
                        (ele.sourceNode.y <= (ele.targetNode.y + ele.targetNode.height) && ele.sourceNode.y >= ele.targetNode.y)
                      )"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2)+'h'+connectorWSelf +
                        'V'+ ((ele.sourceNode.y-ele.targetNode.y ) <= 0? (ele.sourceNode.y - connectorWSelf) : (ele.targetNode.y -connectorWSelf)) +
                        'H' + (ele.targetNode.x - connectorWSelf) +
                        'V' +(ele.targetNode.y + ele.targetNode.height / 2) +
                      'H' + ele.targetNode.x"
                      class="connectorLine"
                    />
                    <!-- 非自连： 2.sourceNode 的右侧箭头X > targetNode的左侧箭头X (3) 且 sourceNode的高度 < targetNode的高度 且 sourceNode的起点 > targetNode的终点 且高度重叠-->
                    <path
                      v-if="ele.sourceNode.id != ele.targetNode.id &&
                        (ele.sourceNode.x + ele.sourceNode.width) >= ele.targetNode.x &&
                        (ele.sourceNode.y + ele.sourceNode.height/2) > (ele.targetNode.y + ele.targetNode.height / 2) &&
                        ((ele.targetNode.y <= (ele.sourceNode.y + ele.sourceNode.height) && ele.targetNode.y >= ele.sourceNode.y) ||
                        (ele.sourceNode.y <= (ele.targetNode.y + ele.targetNode.height) && ele.sourceNode.y >= ele.targetNode.y)
                      )"
                      :d="'M'+(ele.sourceNode.x + ele.sourceNode.width)+','+(ele.sourceNode.y + ele.sourceNode.height / 2)+'h'+connectorWSelf +
                        'V'+ ((ele.sourceNode.y + ele.sourceNode.height-ele.targetNode.y -ele.targetNode.height ) >= 0? (ele.sourceNode.y+ele.sourceNode.height + connectorWSelf) : (ele.targetNode.y+ele.targetNode.height +connectorWSelf)) +
                        'H' + (ele.targetNode.x - connectorWSelf) +
                        'V' +(ele.targetNode.y + ele.targetNode.height / 2) +
                      'H' + ele.targetNode.x"
                      class="connectorLine"
                    />
                  </g>
                  <!-- 动态绘制的连线 -->
                  <g>
                    <line v-show="connectingLine.isConnecting" :x1="connectingLine.x1" :y1="connectingLine.y1" :x2="connectingLine.x2" :y2="connectingLine.y2" stroke="#768699" stroke-width = "2"/>
                  </g>
                </g>
                <line id="xmarker" :class="{isMarkerShow:marker.isMarkerShow}" :y1="marker.xmarkerY" :x2="marker.xmarkerX" :y2="marker.xmarkerY" class="marker" x1="0"/>
                <line id="ymarker" :class="{isMarkerShow:marker.isMarkerShow}" :x1="marker.ymarkerX" :x2="marker.ymarkerX" :y2="marker.ymarkerY" class="marker" y1="0"/>
                <rect v-show="selectionBox.isShow" :x="selectionBox.x" :y="selectionBox.y" :width="selectionBox.width" :height="selectionBox.height" stroke-dasharray="5,5" stroke-width="1" stroke="#222" fill="rgba(170,210,232,0.5)"/>
              </svg>

            <!-- 节点属性设置 Start -->
            <!-- <div id="topoAttrWrap" :class="{active:isTopoAttrShow}">
              <i v-if="isTopoAttrShow" class="fa fa-chevron-circle-right topoAttrArrow" @click="isTopoAttrShow = !isTopoAttrShow"></i>
              <i v-if="!isTopoAttrShow" class="fa fa-chevron-circle-left topoAttrArrow" @click="isTopoAttrShow = !isTopoAttrShow"></i>
              <div id="topoAttrHeader">属性设置框 <i class="fa fa-window-maximize" @click="maxAttrWindow()"></i></div>
              <div v-if="selectNodeIndex === null">
                <div style="padding:50px;text-align:center">没有任何节点属性</div>
              </div>
              <div v-if="selectNodeIndex != null && topoData.nodes.length > 0" style="overflow-y: scroll;height:calc(100% - 40px);padding:20px 15px;box-sizing:border-box">
                <el-form :model="topoData.nodes[selectNodeIndex]" label-width="100px" class="demo-ruleForm" labelPosition="left">
                  <div v-if="topoData.nodes[selectNodeIndex].attrs.length > 0">
                    <el-form-item label="名称">
                      <el-input v-model="topoData.nodes[selectNodeIndex].name"></el-input>
                    </el-form-item>
                  </div>
                  <div v-else id="infoWrap">
                    <i class="infoIcon fa fa-info-circle"></i>暂无属性信息
                  </div>
                  <div>
                    <el-form-item v-if="topoData.nodes[selectNodeIndex].attrs.length > 0 && ele.type !='keyVal'" v-for="(ele, key) in topoData.nodes[selectNodeIndex].attrs" :rules="ele.rules" :label="ele.name" :prop="ele.name" v-bind:key="key">
                      <el-input v-if="ele.type === 'input'" v-model="ele.value" :placeholder="ele.placeholder" :disabled="ele.disabled"></el-input>
                      <el-select v-if="ele.type === 'select'" v-model="ele.value" :placeholder="ele.placeholder" :disabled="ele.disabled">
                        <el-option v-for="(option, key) in ele.options" v-bind:key="key" :label="option.label" :value="option.value"></el-option>
                      </el-select>
                      <el-checkbox-group v-if="ele.type === 'checkbox'" v-model="ele.value" :disabled="ele.disabled">
                        <el-checkbox :label="option.label" v-for="(option,key) in ele.options" v-bind:key="key"></el-checkbox>
                      </el-checkbox-group>
                      <el-input v-if="ele.type === 'textarea'" type="textarea" v-model="ele.value" :disabled="ele.disabled"></el-input>
                      <el-radio-group  v-if="ele.type === 'radio'" v-model="ele.value" :disabled="ele.disabled">
                        <el-radio :label="option.label" v-for="(option,key) in ele.options" v-bind:key="key"></el-radio>
                      </el-radio-group>
                    </el-form-item>
                    <div class="clearfix" style="margin-bottom:10px">
                      <i class="fa fa-plus-square" style="font-size:25px;float:right;cursor:pointer" @click="openKeyValueDialog(selectNodeIndex)"></i>
                    </div>
                    <div class="clearfix" v-for="(ele, key) in topoData.nodes[selectNodeIndex].attrs" v-if="ele.type === 'keyVal'" style="margin:15px 0" v-bind:key="key">
                      <el-input style="float:left; width:20%"  v-model="ele.name" placeholder="key"></el-input>
                      <el-input style="float:left; width:calc(80% - 15px - 25px); margin-left:15px;" v-model="ele.value" placeholder="value"></el-input>
                      <i class="removeAttrArrow fa fa-remove" @click="removeKeyValue(selectNodeIndex, key)"></i>
                    </div>
                  </div>
                </el-form>
              </div>
            </div> -->
            <!-- 节点属性设置 End -->
            </div>
          <!-- 拓扑流程图 End -->

          </div>
        </el-col>
      </el-row>
      <div v-if="toolbarMoveNode.isShow" id="move-node" :style="{ left:toolbarMoveNode.left + 'px', top: toolbarMoveNode.top + 'px' }" class="nodeMoveCss">
        <div class="node-icon">
          <img :src="toolbarMoveNode.icon" class="toolbar-node-icon">
        </div>
        <div class="node-name">{{ toolbarMoveNode.name }}</div>
      </div>
    <!-- <el-dialog title="新增key和value" :visible.sync="keyFormDialog.visible" @close="closeKeyValueDialog('keyFormDialog')">
      <el-form :model="keyFormDialog" :inline="true" :rules="keyFormRules" label-width="50px" label-position="left" ref="keyFormDialog">
        <el-form-item label="key" prop="key">
          <el-input v-model="keyFormDialog.key" auto-complete="off" placeholder="key"></el-input>
        </el-form-item>
        <el-form-item label="value" prop="value">
          <el-input v-model="keyFormDialog.value" auto-complete="off" placeholder="value"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="keyFormDialog.visible = false">取 消</el-button>
        <el-button type="primary" @click="submitKeyValueForm('keyFormDialog')">确 定</el-button>
      </div>
    </el-dialog> -->
    </div>
  </div>
</template>
<script>
import Service from '@/assets/topo/root.png'
import compute from '@/assets/topo/compute.png'
import connectorRules from '@/config/connectorRules.js'
import $ from 'jquery'
import {
  saveSvgAsPng
} from 'save-svg-as-png'

import {
  fetchClusterList,
  fetchHostByCluster,
  fetchDeployDiagram,
  saveDeployDiagram
} from '@/api/serviceDeployment'

export default {
  name: 'ComplexTable',
  filters: {},
  data() {
    return {
      tableKey: 0,
      listQuery: {
        clusterName: ''
      },
      clusterOptions: [],
      // 弹窗
      keyFormDialog: {
        visible: false,
        key: '',
        value: '',
        nodeKey: null
      },
      keyFormRules: {
        key: [{ required: true, message: '请输入key值', trigger: 'blur' }],
        value: [{ required: true, message: '请输入value值', trigger: 'blur' }]
      },
      connectorRules: connectorRules, // 节点间关系的规则
      selectNodeIndex: 0,
      svgAttr: {
        width: 0,
        height: 0,
        isHand: false,
        viewX: 0,
        viewY: 0,
        minW: 0,
        minH: 0,
        isCrosshair: false
      },
      activeNames: ['1'],
      isTopoAttrShow: false,
      svgToolbar: [
        { name: '默认模式', className: 'toolbar-default', isActive: true },
        { name: '框选模式', className: 'toolbar-rectangle_selection', isActive: false }
        // {name:'放大', className:'toolbar-zoomin', isActive: false},
        // {name:'缩小', className:'toolbar-zoomout', isActive: false},
        // {name:'恢复出厂设置', className:'toolbar-zoomreset', isActive: false}
      ],
      hostList: [],
      toolbarNodeList: [],
      deployList: null,
      toolbarMoveNode: {
        left: 0,
        top: 0,
        name: '',
        icon: '',
        isShow: false
      },
      svgTopo: {
        isMoveover: false
      },
      selectionBox: {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        isShow: false
      },
      connectorWSelf: 15, // 自连连线的宽度
      connectorW: 15, // 非自连连线宽度
      containTop: 30, // 包含关系的子node距离父node
      containLeft: 22, // 包含关系的左右距离
      classchoose: false,
      connectingLine: {
        x1: 0,
        y1: 0,
        x2: 0,
        y2: 0,
        isConnecting: true,
        sourceNode: '',
        endNode: ''
      },
      marker: {
        xmarkerY: 0,
        xmarkerX: 0,
        ymarkerX: 0,
        ymarkerY: 0,
        isMarkerShow: false
      },

      // 背景网格数据
      gridData: [
        {
          id: 1,
          x1: 0,
          x2: 100,
          y1: 20,
          y2: 20,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 2,
          x1: 0,
          x2: 100,
          y1: 40,
          y2: 40,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 3,
          x1: 0,
          x2: 100,
          y1: 60,
          y2: 60,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 4,
          x1: 0,
          x2: 100,
          y1: 80,
          y2: 80,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 5,
          x1: 20,
          x2: 20,
          y1: 0,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 6,
          x1: 40,
          x2: 40,
          y1: 0,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 7,
          x1: 60,
          x2: 60,
          y1: 0,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 8,
          x1: 80,
          x2: 80,
          y1: 0,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 1,
          opacity: 0.3
        },
        {
          id: 9,
          x1: 100,
          x2: 100,
          y1: 0,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 2,
          opacity: 0.6
        },
        {
          id: 10,
          x1: 0,
          x2: 100,
          y1: 100,
          y2: 100,
          color: '#c0c0c0',
          strokeWidth: 2,
          opacity: 0.6
        }
      ],
      topoData: {
        nodes: [],
        connectors: []
      }
    }
  },
  created() {
    this.getClusterList()
  },
  mounted() {
    // 初始化：获取topo 组件宽高
    const topoWidth = $('#topo-wrap').width()
    const topoHeight = $('#topo-wrap').height()
    this.marker.xmarkerX = topoWidth
    this.marker.ymarkerY = topoHeight
    this.svgAttr.width = topoWidth
    this.svgAttr.height = topoHeight
    this.svgAttr.minW = topoWidth
    this.svgAttr.minH = topoHeight
    this.deleteNodeAndConnetor() // 绑定删除Node事件
  },
  methods: {

    // 查询所有集群
    getClusterList() {
      fetchClusterList(this.listQuery).then(response => {
        this.clusterOptions = response.data
      })
    },

    // 通过集群名查询主机信息
    getHostByCluster(nodesList) {
      fetchHostByCluster(this.listQuery).then(response => {
        var nodeList = this.hostList = response.data
        if (!nodeList.length || (nodeList.length === nodesList.length)) {
          this.toolbarNodeList = []
          return false
        }

        // 数组对象求差集
        function getSubtract(unionArr, subsetArr) {
          const tempList = []
          for (var i = 0; i < unionArr.length; i++) {
            var flag = true
            for (var j = 0; j < subsetArr.length; j++) {
              if (unionArr[i].hostId === subsetArr[j].hostId) {
                flag = false
              }
            }
            if (flag) {
              tempList.push(unionArr[i])
            }
          }
          return tempList
        }

        if (nodesList && nodesList.length > 0) {
          nodeList = getSubtract(nodeList, nodesList)
        }

        const list = []
        this.hostList = nodeList
        this.hostList.forEach((ele) => {
          const listItem = {
            physical: ele.physical,
            hostId: ele.hostId,
            hostName: ele.hostName,
            saltHost: ele.saltHost,
            relativeHost: ele.relativeHost,
            width: 200,
            height: 30,
            classType: 'T1',
            num: 1
          }

          if (ele.physical) {
            listItem.type = 'Compute'
            listItem.icon = compute
          } else {
            listItem.type = 'Service'
            listItem.icon = Service
          }
          list.push(listItem)
        })
        this.toolbarNodeList = list
      })
    },

    // 通过集群名查询服务部署图信息
    getDeployDiagram(name) {
      if (!name) {
        return false
      }
      this.listQuery.clusterName = name
      fetchDeployDiagram(this.listQuery).then(response => {
        if (response.data) {
          this.topoData = JSON.parse(response.data)
          this.getHostByCluster(this.topoData.nodes)
        } else {
          this.getHostByCluster([])
          this.topoData = {
            nodes: [],
            connectors: []
          }
        }
      })
    },

    // 保存绘制流程图  svg topo转换为json数据
    handleSave() {
      const params = {
        clusterName: this.listQuery.clusterName,
        modelJson: JSON.stringify(this.topoData)
      }
      saveDeployDiagram(params).then(response => {
        this.$notify({
          title: '成功',
          message: '保存成功',
          type: 'success',
          duration: 2000
        })
      })
    },

    // 从左侧hostList删除已移动到svg的host;
    removeHost(item) {
      const list = this.toolbarNodeList
      for (let i = 0; i < list.length; i++) {
        if (list[i].hostId === item.hostId) {
          this.toolbarNodeList.splice(i, 1)
          break
        }
      }
    },
    // 从右侧svg中删除host,并添加到左侧hostList中
    removeSvgHost(item) {
      delete (item.attrs)
      delete (item.containNodes)
      delete (item.id)
      delete (item.name)
      delete (item.initH)
      delete (item.initW)
      delete (item.isLeftConnectShow)
      delete (item.isRightConnectShow)
      delete (item.isSelect)
      delete (item.x)
      delete (item.y)
      this.toolbarNodeList.push(item)
      const list = this.toolbarNodeList
      const obj = {}
      const result = []
      for (var i = 0; i < list.length; i++) {
        if (!obj[list[i].hostId]) {
          result.push(list[i])
          obj[list[i].hostId] = true
        }
      }
      this.toolbarNodeList = result.slice().sort((a, b) => {
        return a.hostId - b.hostId
      })
    },
    // 打开key-value对话框窗口
    openKeyValueDialog(key) {
      this.keyFormDialog.visible = true
      this.keyFormDialog.nodeKey = key
    },

    // 删除属性
    removeKeyValue(selectNodeIndex, attrIndex) {
      const delTxt =
        '此操作将删除  key:' +
        this.topoData.nodes[selectNodeIndex].attrs[attrIndex].name +
        '  value:' +
        this.topoData.nodes[selectNodeIndex].attrs[attrIndex].value +
        '，是否继续？'
      this.$confirm(delTxt, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          this.topoData.nodes[selectNodeIndex].attrs.splice(attrIndex, 1)
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
        })
        .catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          })
        })
    },

    // 提交key-value值
    submitKeyValueForm(formName) {
      this.$refs[formName].validate(valid => {
        if (valid) {
          this.$message({
            message: '新增key和value成功',
            type: 'success'
          })

          const newAttr = {
            type: 'keyVal',
            name: this.keyFormDialog.key,
            value: this.keyFormDialog.value
          }
          this.topoData.nodes[this.keyFormDialog.nodeKey].attrs.push(newAttr)

          // 重置keyFormDialog值
          this.keyFormDialog.visible = false
          this.$refs[formName].resetFields()
        } else {
          return false
        }
      })
    },

    // 关闭key-value框
    closeKeyValueDialog(formName) {
      this.keyFormDialog.visible = false
      this.$refs[formName].resetFields()
    },

    // 全屏属性设置框
    maxAttrWindow() {
      const maxWidth = $('#topo-wrap').width()
      $('#topoAttrWrap').width(maxWidth)
    },

    canConnectorTo(curNodeType, connectorToNodeType, connectorType) {
      // 当需要包含和连线规则的时候 清除以下注释
      let canConnector = false
      if (connectorType === 'Link') {
        this.connectorRules.forEach((ele, key) => {
          if (ele.type === curNodeType) {
            ele.canLinkToType.forEach((el, index) => {
              if (el === connectorToNodeType) canConnector = true
            })
          }
        })
      } else if (connectorType === 'Contain') {
        this.connectorRules.forEach((ele, key) => {
          if (ele.type === curNodeType) {
            ele.canBeContainedType.forEach((el, index) => {
              if (el === connectorToNodeType) canConnector = true
            })
          }
        })
      }
      canConnector = true
      return canConnector
    },

    // 拖拽toolbar中的node
    dragToolbarNode(nodeData, key, event) {
      const NODE = nodeData[key]
      const toolbarName = NODE.type
      const toolbarIcon = NODE.icon
      const topoSVG = $('#topo-svg')
      const svgOffsetLeft = topoSVG.offset().left
      const svgOffsetTop = topoSVG.offset().top
      const svgWidth = topoSVG.width()
      const svgHeight = topoSVG.height()
      let isContainSvgArea = false

      // 在鼠标指针移动到元素上时触发
      document.onmousemove = event => {
        const mouseX = event.clientX // 当前鼠标X轴位置
        const mouseY = event.clientY // 当前鼠标Y轴位置
        const nodeX =
          event.clientX -
          svgOffsetLeft +
          $(document).scrollLeft() +
          this.svgAttr.viewX // svg最终位置
        const nodeY =
          event.clientY -
          svgOffsetTop +
          $(document).scrollTop() +
          this.svgAttr.viewY
        isContainSvgArea = false
        this.toolbarMoveNode.left = mouseX + 4 + $(document).scrollLeft() // 鼠标位置 + 文档滚动的距离
        this.toolbarMoveNode.top = mouseY + 4 + $(document).scrollTop()
        this.toolbarMoveNode.name = toolbarName
        this.toolbarMoveNode.icon = toolbarIcon
        this.toolbarMoveNode.isShow = true
        this.marker.isMarkerShow = false

        // 鼠标滑入svg区域内显示标尺并显示标尺正确位置
        if (
          mouseX >= svgOffsetLeft &&
          mouseX <= svgOffsetLeft + svgWidth &&
          mouseY >= svgOffsetTop - $(document).scrollTop() &&
          mouseY <= svgOffsetTop + svgHeight - $(document).scrollTop()
        ) {
          this.marker.isMarkerShow = true
          isContainSvgArea = true
          const n1 = Math.floor(nodeX / 20) // grid宽高的整数倍
          const n2 = Math.floor(nodeY / 20)
          this.marker.xmarkerY = n2 * 20
          this.marker.ymarkerX = n1 * 20
        }
      }

      // 在鼠标按键被松开时发生
      document.onmouseup = event => {
        document.onmousemove = null
        document.onmouseup = null

        // 判断鼠标在svg区域
        if (isContainSvgArea) {
          const TOPODATA = this.topoData
          const name = NODE.type + '_' + NODE.num
          NODE.num++
          const id = GenNonDuplicateID(5)
          const nodeEndX = this.marker.ymarkerX
          const nodeEndY = this.marker.xmarkerY
          const svgNode = {
            name,
            type: NODE.type,
            id: id,
            hostId: NODE.hostId,
            physical: NODE.physical,
            hostName: NODE.hostName,
            saltHost: NODE.saltHost,
            relativeHost: NODE.relativeHost,
            x: nodeEndX,
            y: nodeEndY,
            icon: NODE.icon,
            width: NODE.width,
            height: NODE.height,
            initW: NODE.width,
            initH: NODE.height,
            classType: NODE.classType,
            isLeftConnectShow: false,
            isRightConnectShow: false,
            containNodes: [],
            attrs: []
          }
          this.marker.isMarkerShow = false // 标尺取消显示
          this.topoData.nodes.push(svgNode) // 创建一个svg Node

          // 计算是否与某个节点重叠
          for (let i = TOPODATA.nodes.length - 1; i >= 0; i--) {
            const node = TOPODATA.nodes[i]
            if (
              node.x <= nodeEndX &&
              nodeEndX <= node.x + node.width &&
              nodeEndY >= node.y &&
              node.y + node.height >= nodeEndY &&
              node.id !== id
            ) {
              const canBeContain = this.canConnectorTo(
                NODE.type,
                node.type,
                'Contain'
              )

              // 判断是否能被包含在目标元素中
              if (canBeContain) {
                const connector = {
                  type: 'Contain',
                  sourceNode: {
                    id: id
                  },
                  targetNode: {
                    id: node.id
                  },
                  isSelect: false
                }
                TOPODATA.connectors.push(connector)
                node.containNodes.push(id) // 如果有嵌套关系，就在父节点放入子节点id
                this.refreshRowAndOuterNode(svgNode) // 刷新并列节点位置和父节点宽高
                this.refreshConnectorsData()
                break
              }
            }
          }
          this.removeHost(NODE)
        }

        // 重新初始toolbarMoveNode的值
        this.toolbarMoveNode.left = 0
        this.toolbarMoveNode.top = 0
        this.toolbarMoveNode.name = ''
        this.toolbarMoveNode.icon = ''
        this.toolbarMoveNode.isShow = false
      }

      // 生成唯一id值
      function GenNonDuplicateID(randomLength) {
        return Number(
          Math.random()
            .toString()
            .substr(3, randomLength) + Date.now()
        ).toString(36)
      }
    },

    // svg区域事件
    // moveInTopoSvg(){
    //   //this.svgTopo.isMoveover = true
    // },
    // moveOutTopoSvg(){
    //   // this.svgTopo.isMoveover = false
    //   // this.marker.isMarkerShow = false
    // },

    // 1.取消选中的node节点 2. 移动viewbox
    mousedownTopoSvg(event) {
      const mouseX0 = event.clientX // 鼠标点击下的位置
      const mouseY0 = event.clientY
      const startViewX = this.svgAttr.viewX
      const startViewY = this.svgAttr.viewY
      const startSvgW = this.svgAttr.width
      const startSvgH = this.svgAttr.height
      const svgMinW = this.svgAttr.minW
      const svgMinH = this.svgAttr.minH
      let selectionBoxX = 0
      let selectionBoxY = 0
      this.cancelAllNodesSelect() // 取消所有节点选中
      this.cancelAllLinksSelect() // 取消连线选中
      if (this.svgToolbar[1].isActive) {
        selectionBoxX =
          event.clientX -
          $('#topo-svg').offset().left +
          $(document).scrollLeft() +
          this.svgAttr.viewX
        selectionBoxY =
          event.clientY -
          $('#topo-svg').offset().top +
          4 +
          $(document).scrollTop() +
          this.svgAttr.viewY
        this.selectionBox.isShow = true
        this.selectionBox.x = selectionBoxX
        this.selectionBox.y = selectionBoxY
      }
      // 移动viewbox位置
      document.onmousemove = event => {
        const disX = event.clientX - mouseX0
        const disY = event.clientY - mouseY0
        const endSvgW = startSvgW - disX
        const endSvgH = startSvgH - disY

        if (this.svgToolbar[1].isActive) {
          const selectionW = Math.abs(disX)
          const selectionH = Math.abs(disY)
          this.svgAttr.isCrosshair = true
          if (disX <= 0) {
            this.selectionBox.x = selectionBoxX + disX
          } else {
            this.selectionBox.x = selectionBoxX
          }
          if (disY <= 0) {
            this.selectionBox.y = selectionBoxY + disY
          } else {
            this.selectionBox.y = selectionBoxY
          }
          this.selectionBox.width = selectionW
          this.selectionBox.height = selectionH
          return false
        }
        this.svgAttr.isHand = true
        this.svgAttr.viewX = startViewX <= disX ? 0 : startViewX - disX // 根据鼠标移动的位移，得到视图移动位移
        this.svgAttr.viewY = startViewY <= disY ? 0 : startViewY - disY
        this.svgAttr.width = endSvgW < svgMinW ? svgMinW : endSvgW // 动态设置svg宽高
        this.svgAttr.height = endSvgH < svgMinH ? svgMinH : endSvgH
        this.marker.xmarkerX = this.svgAttr.width
        this.marker.ymarkerY = this.svgAttr.height
      }
      // 在鼠标按键被松开时发生
      document.onmouseup = event => {
        document.onmousemove = null
        document.onmouseup = null
        this.svgAttr.isHand = false
        this.svgAttr.isCrosshair = false

        // 如果是框选模式
        if (this.svgToolbar[1].isActive) {
          const selectionBoxObj = this.selectionBox
          const sW = selectionBoxObj.width
          const sH = selectionBoxObj.height
          const sX = selectionBoxObj.x
          const sY = selectionBoxObj.y
          this.topoData.nodes.forEach((node, key) => {
            if (
              sX <= node.x &&
              sY <= node.y &&
              node.x + node.width <= sX + sW &&
              node.y + node.height <= sY + sH
            ) {
              node.isSelect = true
            }
          })
          this.selectionBox.isShow = false
          this.selectionBox.x = 0
          this.selectionBox.y = 0
          this.selectionBox.width = 0
          this.selectionBox.height = 0
        }
      }
    },

    // 拖拽svg中的node
    dragSvgNode(key, event) {
      const mouseX0 = event.clientX + $(document).scrollLeft() // 鼠标点击下的位置
      const mouseY0 = event.clientY + $(document).scrollTop()
      const CURNODE = this.topoData.nodes[key] // 点击的node对象
      const startX = CURNODE.x // 节点开始位置
      const startY = CURNODE.y
      // const curNodeId = CURNODE.id // 当前结点id
      // const nodeW = CURNODE.width // 节点 宽高
      // const nodeH = CURNODE.height
      const nodeStartPosArr = []
      // const moveDis = false
      this.marker.isMarkerShow = true // 显示标尺

      // 把选中的node信息放入数组最后一位，待看结果 可能有bug
      this.topoData.nodes.splice(key, 1)
      this.topoData.nodes.push(CURNODE)

      /** ** 优化  ****/
      this.putInnerNodeLast(CURNODE) // 递归循环将嵌套节点依次放置，判断包含关系，如果内部有子node，则需要将子node放入数组最后的位置
      this.cancelAllNodesSelect() // 取消所有节点选中
      this.cancelAllLinksSelect() // 取消所有连线选中

      // 节点选中
      CURNODE.isSelect = true
      this.storeCurnodeStartPosition(CURNODE, nodeStartPosArr) // 将选择的node的子子节点初始位置保存进去
      this.topoData.nodes.forEach((node, key) => {
        // 关联属性设置框
        if (node.id === CURNODE.id) this.selectNodeIndex = key
      })

      // 在鼠标指针移动到元素上时触发
      document.onmousemove = event => {
        let disX = event.clientX - mouseX0 + $(document).scrollLeft() // 移动位置
        let disY = event.clientY - mouseY0 + $(document).scrollTop()
        let endX = startX + disX // 最终位置
        let endY = startY + disY
        let n1 = Math.floor(endX / 20) // grid宽高的整数倍
        let n2 = Math.floor(endY / 20)
        if (n1 <= 0) n1 = 0
        if (n2 <= 0) n2 = 0
        if (endX <= 0) {
          endX = 0
          disX = -startX
        }
        if (endY <= 0) {
          endY = 0
          disY = -startY
        }
        this.marker.isMarkerShow = true // 显示标尺
        this.marker.xmarkerY = n2 * 20 // 标尺的移动位置，以每格20的距离移动
        this.marker.ymarkerX = n1 * 20
        this.moveContianNode(disX, disY, nodeStartPosArr) // 根据保存的数组数据移动相关节点
        this.refreshConnectorsData() // 及时更新连线数据
      }

      // 在鼠标按键被松开时发生
      document.onmouseup = event => {
        document.onmousemove = null
        document.onmouseup = null
        this.marker.isMarkerShow = false // 隐藏标尺
        const NodeEndX = this.marker.ymarkerX // 最终位置为标尺的位置 最终节点位置
        const NodeEndY = this.marker.xmarkerY
        const disX = NodeEndX - startX
        const disY = NodeEndY - startY
        const mouseDisX = event.clientX - mouseX0
        const mouseDisY = event.clientY - mouseY0
        this.moveContianNode(disX, disY, nodeStartPosArr) // 移动包含着的子节点
        this.drawContainLayout(
          CURNODE,
          NodeEndX,
          NodeEndY,
          true,
          nodeStartPosArr,
          mouseDisX,
          mouseDisY,
          startY
        )
        this.refreshConnectorsData() // 最后刷新连线
      }
    },

    // 绘制contain布局及刷新连线数据
    drawContainLayout(
      CURNODE,
      NodeEndX,
      NodeEndY,
      isStop,
      nodeStartPosArr,
      mouseDisX,
      mouseDisY,
      startY
    ) {
      const TOPODATA = this.topoData
      const curNodeId = CURNODE.id
      const nodeW = CURNODE.width
      const nodeH = CURNODE.height
      let originTargetNodeId = '' // 原先的targetNode
      let originTargetNode = {}

      // 预留 ++++ 判断是否能增加包含关系
      const NodePoint1 = [NodeEndX, NodeEndY] // 初始当前节点四个角的位置
      const NodePoint2 = [NodeEndX + nodeW, NodeEndY]
      const NodePoint3 = [NodeEndX + nodeW, NodeEndY + nodeH]
      const NodePoint4 = [NodeEndX, NodeEndY + nodeH]

      // 如果点击的node有contain关系，先记录下targetNode
      TOPODATA.connectors.forEach((ele, key) => {
        if (ele.type === 'Contain' && ele.sourceNode.id === curNodeId) {
          originTargetNodeId = ele.targetNode.id
        }
      })

      if (originTargetNodeId) {
        TOPODATA.nodes.forEach((node, key) => {
          if (node.id === originTargetNodeId) originTargetNode = node
        })
      }
      // 情况一：移出后依然恢复原来的位置，前提：1.移除的距离在一定范围 2.点击的节点有父层包含关系
      const endNodeY = startY + mouseDisY
      if (
        originTargetNode &&
        Math.abs(mouseDisX) <= this.containLeft &&
        endNodeY < originTargetNode.y + originTargetNode.height &&
        endNodeY > originTargetNode.y - CURNODE.height
      ) {
        this.refreshRowAndOuterNode(originTargetNode)
        return false
      }
      // 清除当前node的包含关系
      this.deleteCurNodeContainConnector(CURNODE)
      // 与NodeData对比，判断是否有值与其他Node重合的
      var isContainNode = false
      let overlapTargetNode = {}
      for (let i = TOPODATA.nodes.length - 1; i >= 0; i--) {
        // forEach无法跳出循环, 暂用for循环
        const targetNode = TOPODATA.nodes[i]
        isContainNode = false // 初始isContainNode为false的值
        if (CURNODE.id !== targetNode.id) {
          // 排除自身元素
          const minX = targetNode.x
          const maxX = targetNode.x + targetNode.width
          const minY = targetNode.y
          const maxY = targetNode.y + targetNode.height
          const canContianTargetNode = this.canConnectorTo(
            CURNODE.type,
            targetNode.type,
            'Contain'
          )
          // 确认是否能被包含
          // 四种包含情况判重合
          if (
            NodePoint1[0] <= maxX &&
            NodePoint1[0] >= minX &&
            NodePoint1[1] <= maxY &&
            NodePoint1[1] >= minY
          ) { isContainNode = true }
          if (
            NodePoint2[0] <= maxX &&
            NodePoint2[0] >= minX &&
            NodePoint2[1] <= maxY &&
            NodePoint2[1] >= minY
          ) { isContainNode = true }
          if (
            NodePoint4[0] <= maxX &&
            NodePoint4[0] >= minX &&
            NodePoint4[1] <= maxY &&
            NodePoint4[1] >= minY
          ) { isContainNode = true }
          if (
            NodePoint3[0] <= maxX &&
            NodePoint3[0] >= minX &&
            NodePoint3[1] <= maxY &&
            NodePoint3[1] >= minY
          ) { isContainNode = true }
          if (isContainNode && canContianTargetNode) {
            overlapTargetNode = targetNode
            break
          }
        }
      }
      // 选中的node 有 与其他node 重合
      if (isContainNode) {
        // 关系数组中增加包含关系
        const connector = {
          type: 'Contain',
          sourceNode: {
            id: CURNODE.id
          },
          targetNode: {
            id: overlapTargetNode.id
          },
          isSelect: false
        }
        TOPODATA.connectors.push(connector)
        // 如果有嵌套关系，就在父节点放入子节点id
        TOPODATA.nodes.forEach((node, key) => {
          if (node.id === overlapTargetNode.id) { node.containNodes.push(CURNODE.id) }
        })
        this.refreshRowAndOuterNode(CURNODE) // 刷新并列节点位置和父节点宽高
      }
      // 移动包含着的子节点
      if (isContainNode) {
        nodeStartPosArr.forEach((node, key) => {
          if (node.id === CURNODE.id) {
            const disX = CURNODE.x - node.x
            const disY = CURNODE.y - node.y
            this.moveContianNode(disX, disY, nodeStartPosArr)
          }
        })
      }
      // 如果初始targetNodeId 与现在重合的taregtNodeId不同，让originTargetNode位置重置
      if (originTargetNodeId && originTargetNodeId !== overlapTargetNode.id) {
        this.refreshRowAndOuterNode(originTargetNode)
      }
    },
    // 计算是否与其他节点包含
    computedIsContain(CURNODE) {},
    // 存入node及其子节点位置信息
    storeCurnodeStartPosition(CURNODE, startNodePosition) {
      const containNodes = CURNODE.containNodes
      startNodePosition.push({ id: CURNODE.id, x: CURNODE.x, y: CURNODE.y })
      if (containNodes.length) {
        containNodes.forEach((nodeId, key) => {
          this.topoData.nodes.forEach((ele, index) => {
            if (ele.id === nodeId) {
              this.storeCurnodeStartPosition(ele, startNodePosition)
            }
          })
        })
      }
    },
    // contain情况下移动子节点位置
    moveContianNode(disX, disY, nodeStartPosArr) {
      nodeStartPosArr.forEach((ele, key) => {
        const storeInfoId = ele.id
        this.topoData.nodes.forEach((node, key) => {
          if (node.id === storeInfoId) {
            node.x = ele.x + disX
            node.y = ele.y + disY
          }
        })
      })
    },
    // 将选中的容器的最内的容器放置在数组最后
    putInnerNodeLast(CURNODE) {
      const curNodeId = CURNODE.id
      this.topoData.connectors.forEach((ele, key) => {
        if (ele.type === 'Contain' && ele.targetNode.id === curNodeId) {
          const childNodeId = ele.sourceNode.id
          this.topoData.nodes.forEach((node, index) => {
            if (node.id === childNodeId) {
              const childNode = node
              this.topoData.nodes.splice(index, 1)
              this.topoData.nodes.push(childNode)
              this.putInnerNodeLast(childNode)
            }
          })
        }
      })
    },

    // 清除当前选中元素的Contain关系
    deleteCurNodeContainConnector(CURNODE) {
      const curNodeId = CURNODE.id
      this.topoData.connectors.forEach((ele, key) => {
        if (ele.type === 'Contain' && ele.sourceNode.id === curNodeId) {
          const targetNodeId = ele.targetNode.id
          // 1.删除cennetors关系
          this.topoData.connectors.splice(key, 1)
          // 2.删除contains 里面的关系
          this.topoData.nodes.forEach((node, key) => {
            if (node.id === targetNodeId) {
              if (node.containNodes.length) {
                node.containNodes.forEach((ele, key) => {
                  const targetNode = node
                  if (ele === curNodeId) {
                    targetNode.containNodes.splice(key, 1)
                  }
                })
              }
            }
          })
        }
      })
    },
    // 刷新外部node的宽度（递归） 且 刷新右侧所欲并列节点宽度
    refreshOuterNodeWidth(CURNODE) {
      this.topoData.connectors.forEach((ele, key) => {
        if (ele.sourceNode.id === CURNODE.id && ele.type === 'Contain') {
          const targetNodeId = ele.targetNode.id
          this.topoData.nodes.forEach((node, index) => {
            if (node.id === targetNodeId) {
              node.width = 2 * this.containLeft + CURNODE.width
              node.height = 10 + CURNODE.height + this.containTop
              this.refreshOuterNodeWidth(node)
            }
          })
        }
      })
    },
    // 刷新父节点的宽度 及 其子节点位置
    refreshRowAndOuterNode(TARGETNODE) {
      if (TARGETNODE.containNodes.length > 0) {
        // 重新计算targetnode的宽度
        let sumWidth = 0
        let maxHeight = 0
        TARGETNODE.containNodes.forEach((ele, key) => {
          const containNodeId = ele
          this.topoData.nodes.forEach((node, index) => {
            if (node.id === containNodeId) {
              sumWidth += node.width
              if (node.height > maxHeight) maxHeight = node.height
            }
          })
        })
        sumWidth += (TARGETNODE.containNodes.length + 1) * this.containLeft
        TARGETNODE.width = sumWidth
        TARGETNODE.height = maxHeight + 10 + this.containTop
      } else {
        TARGETNODE.width = TARGETNODE.initW
        TARGETNODE.height = TARGETNODE.initH
      }
      this.topoData.connectors.forEach((ele, key) => {
        let parentNodeId = ''
        // const parentNode = {}
        if (ele.sourceNode.id === TARGETNODE.id && ele.type === 'Contain') {
          parentNodeId = ele.targetNode.id
          this.topoData.nodes.forEach((node, key) => {
            if (node.id === parentNodeId) this.refreshRowAndOuterNode(node)
          })
        }
      })

      // 重新计算每个containNode的位置
      this.refreshContainNodesPosition(TARGETNODE)
    },
    // 计算每个containNode的位置
    refreshContainNodesPosition(TARGETNODE) {
      TARGETNODE.containNodes.forEach((ele, key) => {
        const containNodeId = ele
        let containNode
        let preNode
        this.topoData.nodes.forEach((node, index) => {
          if (node.id === containNodeId) {
            containNode = node
          }
        })
        if (key === 0) {
          this.refreshRowNodesPosition(TARGETNODE, containNode, null)
        } else {
          const preNodeIndex = key - 1
          const preNodeId = TARGETNODE.containNodes[preNodeIndex]
          this.topoData.nodes.forEach((node, index) => {
            if (node.id === preNodeId) preNode = node
          })
          this.refreshRowNodesPosition(TARGETNODE, containNode, preNode)
        }
      })
    },
    // 计算并列的nodes位置
    refreshRowNodesPosition(TARGETNODE, CURNODE, PRENODE) {
      if (PRENODE != null) {
        CURNODE.x = PRENODE.x + PRENODE.width + this.containLeft
      } else {
        CURNODE.x = TARGETNODE.x + this.containLeft
      }
      CURNODE.y = TARGETNODE.y + this.containTop
      this.refreshContainNodesPosition(CURNODE)
    },

    // 刷新连线数据
    refreshConnectorsData() {
      this.topoData.connectors.forEach((item, index) => {
        // 更新connectors里的数据
        this.topoData.nodes.forEach((node, key) => {
          if (item.sourceNode.id === node.id) {
            item.sourceNode.width = node.width
            item.sourceNode.height = node.height
            item.sourceNode.x = node.x
            item.sourceNode.y = node.y
          }
          if (item.targetNode.id === node.id) {
            item.targetNode.width = node.width
            item.targetNode.height = node.height
            item.targetNode.x = node.x
            item.targetNode.y = node.y
          }
        })
      })
    },

    // 动态绘制连线
    drawConnectLine(key, event) {
      const CONNECTLINE = this.connectingLine // 绘制连线对象
      const CURNODE = this.topoData.nodes[key] // 当前点击node
      const nodeW = CURNODE.width // 当前node宽高
      const nodeH = CURNODE.height
      const sourceNodeX = CURNODE.x
      const sourceNodeY = CURNODE.y
      const mouseX0 = event.clientX
      const mouseY0 = event.clientY
      const x1 =
        event.clientX -
        $('#topo-svg').offset().left -
        2 +
        $(document).scrollLeft() +
        this.svgAttr.viewX // 连线开始位置的位置：鼠标点击的实际位置   为鼠标位置 - 当前元素的偏移值
      const y1 =
        event.clientY -
        $('#topo-svg').offset().top +
        4 +
        $(document).scrollTop() +
        this.svgAttr.viewY
      CONNECTLINE.isConnecting = true // 显示绘制连线
      CONNECTLINE.x1 = x1
      CONNECTLINE.y1 = y1
      CONNECTLINE.x2 = x1 // 连线终点同样赋值为起点值
      CONNECTLINE.y2 = y1
      CONNECTLINE.sourceNode = CURNODE.id // 将当前点击nodeid值赋给连线起点
      document.onmousemove = event => {
        const disX = event.clientX - mouseX0
        const disY = event.clientY - mouseY0
        const x2 = x1 + disX
        const y2 = y1 + disY
        CURNODE.isRightConnectShow = true
        CONNECTLINE.x2 = x2
        CONNECTLINE.y2 = y2
      }
      document.onmouseup = () => {
        document.onmousemove = null
        document.onmouseup = null
        let hasConnected = false // 标记是否已经有过连线
        const CONNECTORS = this.topoData.connectors
        const sourceNodeW = nodeW
        const sourceNodeH = nodeH
        let targetNodeW = 0 // 目标节点相关信息
        let targetNodeH = 0
        let targetNodeX = 0
        let targetNodeY = 0
        let targetNodeType = ''
        let connectType = ''

        if (CONNECTLINE.endNode) {
          // 正确连线：添加连线信息在connectors中

          // 判断是否有已经有连线的情况
          CONNECTORS.forEach((item, index) => {
            if (
              item.sourceNode.id === CURNODE.id &&
              item.targetNode.id === CONNECTLINE.endNode &&
              item.type === 'Line'
            ) { hasConnected = true }
          })
          // 未连线情况下增加两者连线
          if (!hasConnected) {
            connectType = 'Line'
            // 获取目标节点宽高
            this.topoData.nodes.forEach((item, index) => {
              if (item.id === CONNECTLINE.endNode) {
                targetNodeW = item.width
                targetNodeH = item.height
                targetNodeX = item.x
                targetNodeY = item.y
                targetNodeType = item.type
              }
            })
            const canLinkToTargetNode = this.canConnectorTo(
              CURNODE.type,
              targetNodeType,
              'Link'
            )
            if (!canLinkToTargetNode) {
              this.$message({
                showClose: true,
                message:
                  CURNODE.type + '类型 不能连接 ' + targetNodeType + '类型',
                type: 'error'
              })
              CURNODE.isRightConnectShow = false // 连线失败：起点右侧箭头暂且设置为消失
              CONNECTORS.forEach((item, key) => {
                // 连线判断，如果已经有连线起点为当前的node，将起点箭头设置为显示
                this.topoData.nodes.forEach((node, key) => {
                  if (node.id === item.sourceNode.id && item.type === 'Line') { node.isRightConnectShow = true }
                })
              })
            } else {
              // 类型：包含
              const connector = {
                type: connectType,
                targetNode: {
                  x: targetNodeX,
                  y: targetNodeY,
                  id: CONNECTLINE.endNode,
                  width: targetNodeW,
                  height: targetNodeH
                },
                sourceNode: {
                  x: sourceNodeX,
                  y: sourceNodeY,
                  id: CURNODE.id,
                  width: sourceNodeW,
                  height: sourceNodeH
                }
              }
              CURNODE.isRightConnectShow = true
              this.topoData.nodes.forEach((item, key) => {
                if (item.id === CONNECTLINE.endNode) { item.isLeftConnectShow = true }
              })
              CONNECTORS.push(connector)
            }
          }
        } else {
          CURNODE.isRightConnectShow = false // 连线失败：起点右侧箭头暂且设置为消失
          CONNECTORS.forEach((item, key) => {
            // 连线判断，如果已经有连线起点为当前的node，将起点箭头设置为显示
            this.topoData.nodes.forEach((node, key) => {
              if (node.id === item.sourceNode.id && item.type === 'Line') { node.isRightConnectShow = true }
            })
          })
        }
        // 绘制连线恢复初始值
        CONNECTLINE.x1 = 0
        CONNECTLINE.y1 = 0
        CONNECTLINE.x2 = 0
        CONNECTLINE.y2 = 0
        CONNECTLINE.isConnecting = false
        CONNECTLINE.sourceNode = ''
        CONNECTLINE.endNode = ''
      }
    },
    // 鼠标滑过node
    mouseoverNode(key, event) {
      this.marker.xmarkerY = this.topoData.nodes[key].y
      this.marker.ymarkerX = this.topoData.nodes[key].x
      this.getConnectLine(key)
    },
    // 获取连线终点时的node的ID值
    getConnectLine(key) {
      this.connectingLine.endNode = this.topoData.nodes[key].id
    },

    // 鼠标划出左侧箭头时，将connectingLine.endNode再次初始化
    mouseoutLeftConnector(key) {
      this.connectingLine.endNode = ''
    },

    // 点击选中连线
    selectConnectorLine(key) {
      const connectors = this.topoData.connectors
      // const nodes = this.topoData.nodes
      const selectLine = this.topoData.connectors[key]
      const lastIndex = connectors.length - 1
      connectors.splice(key, 1)
      connectors.push(selectLine)

      // 取消所有选中样式
      this.cancelAllNodesSelect()
      this.cancelAllLinksSelect()
      selectLine.isSelect = true
      this.$set(connectors, lastIndex, selectLine)
    },

    // 取消所有节点选中
    cancelAllNodesSelect() {
      this.topoData.nodes.forEach((ele, key) => {
        ele.isSelect = false
        this.$set(this.topoData.nodes, key, ele)
      })
    },

    // 取消所有连线选中
    cancelAllLinksSelect() {
      this.topoData.connectors.forEach((ele, key) => {
        ele.isSelect = false
        this.$set(this.topoData.connectors, key, ele)
      })
    },

    // 删除node节点及其关系
    deleteNodeAndConnetor(delNode) {
      document.onkeydown = event => {
        const keycode = event.which // 键盘值
        if (keycode === 46) {
          // 单节点和多选删除节点
          for (let i = 0; i < this.topoData.nodes.length; i++) {
            const node = this.topoData.nodes[i]
            if (node.isSelect) {
              this.deleteSelectNodeLink(node.id)
              let targetNodeId = ''
              // const targetNode = null
              this.topoData.connectors.forEach((ele, key) => {
                if (ele.sourceNode.id === node.id) { targetNodeId = ele.targetNode.id }
              })
              this.deleteCurNodeContainConnector(node)
              if (targetNodeId) {
                this.topoData.nodes.forEach((node, index) => {
                  if (node.id === targetNodeId) { this.refreshRowAndOuterNode(node) }
                })
              }
              this.topoData.nodes.splice(i, 1)
              // 删除包含关系：
              // 1.如果有父元素，恢复父元素的宽高位置
              this.deleteCurnodeAndChildnodes(node) // 删除此节点内部所有包含的节点及其关系
              this.refreshNodeArrows() // 刷新节点的左右箭头展示
              i--
              if (this.topoData.nodes.length > 0) {
                this.selectNodeIndex = 0
              } else {
                this.selectNodeIndex = null
                this.isTopoAttrShow = false
              }
              if (node.hostId && node.hostName) {
                this.removeSvgHost(node)
              }
            }
          }

          // 单选删除连线功能
          this.topoData.connectors.forEach((ele, key) => {
            if (ele.isSelect) {
              this.topoData.connectors.splice(key, 1)
              this.refreshNodeArrows() // 重新绘制node节点左右箭头
            }
          })

          this.refreshConnectorsData()
        }
      }
    },

    // 删除选中node的连线
    deleteSelectNodeLink(selectId) {
      const connectorObjArr = this.topoData.connectors
      let connectorsLen = connectorObjArr.length
      for (let i = 0; i < connectorsLen; i++) {
        const connectorObj = connectorObjArr[i]
        // 删除连线
        if (
          connectorObj.type === 'Line' &&
          (connectorObj.sourceNode.id === selectId ||
            connectorObj.targetNode.id === selectId)
        ) {
          this.topoData.connectors.splice(i, 1)
          i--
          connectorsLen--
        }
      }
    },

    // 删除此节点下所有包含的所有节点
    deleteCurnodeAndChildnodes(CURNODE) {
      this.deleteCurNodeContainConnector(CURNODE)
      if (CURNODE.containNodes.length) {
        CURNODE.containNodes.forEach((containNodeId, key) => {
          const containId = containNodeId
          this.topoData.nodes.forEach((ele, index) => {
            if (ele.id === containId) {
              const curnode = ele
              this.topoData.nodes.splice(index, 1)
              this.deleteSelectNodeLink(containId)
              this.deleteCurnodeAndChildnodes(curnode) // 递归删除内部所有的节点及其关系
            }
          })
        })
      }
    },

    // 重新绘制node节点左右箭头
    refreshNodeArrows() {
      this.topoData.nodes.forEach((topoNode, index) => {
        topoNode.isLeftConnectShow = false
        topoNode.isRightConnectShow = false
      })
      this.topoData.connectors.forEach((ele, key) => {
        const sourceNodeId = ele.sourceNode.id
        const targetNodeId = ele.targetNode.id
        if (ele.type === 'Line') {
          this.topoData.nodes.forEach((topoNode, index) => {
            if (topoNode.id === targetNodeId) topoNode.isLeftConnectShow = true
            if (topoNode.id === sourceNodeId) topoNode.isRightConnectShow = true
          })
        }
      })
    },

    // svg工具栏选择工具
    selectToolbar(key) {
      this.svgToolbar.forEach((ele, key) => {
        ele.isActive = false
      })
      this.svgToolbar[key].isActive = true
    },

    // 获取当前时间
    dateFormat() {
      const date = new Date()
      const year = date.getFullYear()
      const month = date.getMonth()
      const day = date.getDate()
      const hour = date.getHours()
      const minute = date.getMinutes()
      const second = date.getSeconds()
      return year + '' + month + '' + day + '' + hour + '' + minute + '' + second
    },

    // 保存成图片
    saveTopoImage() {
      let maxW = 0
      let maxH = 0
      const initW = this.svgAttr.width
      const initH = this.svgAttr.height
      this.topoData.nodes.forEach((node, key) => {
        const nodeEndX = node.width + node.x
        const nodeEndY = node.height + node.y
        if (nodeEndX > maxW) maxW = nodeEndX
        if (nodeEndY > maxH) maxH = nodeEndY
      })
      maxW = maxW < this.svgAttr.minW ? this.svgAttr.minW : maxW
      maxH = maxH < this.svgAttr.minH ? this.svgAttr.minH : maxH
      this.svgAttr.width = maxW + 50
      this.svgAttr.height = maxH + 20
      const svgPngName = 'CMDB_服务部署图' + this.dateFormat() + '.png'

      saveSvgAsPng(document.getElementById('topo-svg'), svgPngName)

      // 建议使用promise进行优化
      setTimeout(() => {
        this.svgAttr.width = initW
        this.svgAttr.height = initH
      }, 1000)
    }
  }
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
// 当前页主要样式
.fa {
  margin-right:4px !important;
}

.app-main,
.topo-box {
  -webkit-user-select: none;
  user-select: none;
  -moz-select: none;
  -ms-select: none;
  -o-select: none;
}

// topo主体样式
#topo-wrap {
  height: calc(100% - 40px);
}

/*toolbar node样式*/
.node-item {
  margin-top: 5px;
  cursor: pointer;
  border: 1px solid #c7d1dd;
  -webkit-user-select: none;
  user-select: none;
}
.node-css {
  background-color: #fff;
  -webkit-user-select: none;
  user-select: none;
  box-sizing: border-box;
  padding: 8px 0;
}
.nodeMoveCss {
  width: 57px;
  height: 57px;
  background-color: #fff;
  -webkit-user-select: none;
  user-select: none;
  box-sizing: border-box;
  padding: 5px;
}
.node-icon {
  width:28px;
  height:28px;
  line-height:28px;
  display: inline-block;
  text-align: center;
  -webkit-user-select: none;
  user-select: none;
  margin:0 5px 0 6px;
}
.toolbar-node-icon {
  width: 28px;
  height: 28px;
  -webkit-user-select: none;
  user-select: none;
}
.node-name {
  width:130px;
  height:28px;
  line-height:28px;
  display: inline-block;
  font-size: 12px;
  text-align: left;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  -webkit-user-select: none;
  user-select: none;
  color: #000;
}

#topoComponent {
  width: 100%;
  box-sizing: border-box;
  background-color: #fff;
  height: 100%;
  .topoRow,
  .svgCol {
    height: 100%;
  }
  .topoCol{
    height:660px;
  }

  /* toolbar样式 */
  #toolbar {
    height: 100%;
    box-sizing: border-box;

    // 左侧操作栏title
    .toolbar-head {
      height: 40px;
      line-height: 40px;
      text-align: center;
      font-size: 14px;
      -webkit-user-select: none;
      user-select: none;
      font-weight: 700;
      color: #000069;
      text-overflow: ellipsis;
      overflow: hidden;
      white-space: nowrap;
    }

    // 左侧操作栏
    .toolbar-main {
      height: 650px;
      overflow-y: auto;
      box-sizing: border-box;
      padding: 10px;
      li {
        list-style: none;
      }
      .no-list {
        margin-top: 100%;
        line-height: 44px;
        text-align: center;
      }
    }
  }
}

.topoRow {
  border: 1px solid #adadad;
  border-radius: 2px;
}

/*移动的node*/
#move-node {
  position: absolute;
  border: 1px solid #768699;
  box-sizing: border-box;
}
.reactClass.isSelect {
  stroke: red;
  stroke-width: 3;
}
.marker {
  stroke: #3d7ed5;
  stroke-width: 1;
  display: none;
}
.isMarkerShow {
  display: block;
}
.connectorArror,.removeEle {
  display: none;
}
.nodesG {
  -webkit-user-select: none;
  user-select: none;
  -moz-select: none;
  -ms-select: none;
  -o-select: none;
}
.nodesG:hover .connectorArror, .nodesG:hover .removeEle {
  display: block;
}
.nodeImg {
  -webkit-user-select: none;
  user-select: none;
  -moz-select: none;
  -ms-select: none;
  -o-select: none;
}
.nodeName {
  font-size: 12px;
  fill: #768699;
  -webkit-user-select: none;
  user-select: none;
}
.connector {
  display: block;
}
.connectorsG .connectorLine {
  stroke: #768699;
  stroke-width: 2;
  fill: none;
}
.connectorsG.active .connectorLine {
  stroke: red;
  stroke-width: 3;
}
.reactClass {
  stroke-width: 2;
  stroke: #768699;
  fill: #fff;
  cursor: default;
}
.circleColor {
  fill: #768699;
}

.circleCancel {
  cursor: pointer;
  fill: #F56C6C;
}

/** svg tools **/
#svgWrap {
  height: 100%;
  box-sizing: border-box;

  // head tools
  #svgHead {
    width: 100%;
    height: 60px;
    box-sizing: border-box;
    border: solid #adadad;
    border-width: 0px 1px 0 1px;
    padding: 5px;
    ul {
      padding: 0;
      margin: 10px 0 0;
      li {
        list-style: none;
        margin-right: 15px;
      }
    }
    .svgHeadItemLst {
      margin-left: 10px;
      li {
        margin: 0;
      }
      .svgHeadItem {
        padding: 5px 10px;
        border: 1px solid #adadad;
        cursor: pointer;
        float: left;
        list-style: none;
        border-left-width: 0;
      }
      .svgHeadItem:hover {
        background-color: #ebebeb;
      }
      .svgHeadItem:first-child {
        border-left-width: 1px;
      }
      .svgHeadItem.active {
        background-color: #ebebeb;
        box-shadow: 2px 2px 1px #ccc inset;
      }
    }
  }

  // topo area
  #topo-wrap {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid #adadad;
    overflow: hidden;
    border-bottom: 0;
    height: 600px;

    #topo-svg {
      box-sizing: border-box;
      background-color: #fff;
      -webkit-user-select: none;
      user-select: none;
      -moz-select: none;
      -ms-select: none;
      -o-select: none;
    }
    #topo-svg.hand {
      cursor: pointer;
    }
    #topo-svg.crosshair {
      cursor: crosshair;
    }

    /**
  ** 浮层属性设置框
  **/
    #topoAttrWrap {
      height: 100%;
      width: 400px;
      position: absolute;
      top: 0;
      right: -400px;
      background: #fff;
      border-left: 1px solid #000069;
      transition: all 1s;
      box-sizing: border-box;
      #topoAttrHeader {
        padding: 10px 0;
        background-color: #000069;
        color: #fff;
        text-align: center;
      }
      .topoAttrArrow {
        width: 20px;
        height: 20px;
        background: red;
        color: #000069;
        font-size: 20px;
        position: absolute;
        top: 50%;
        left: -10px;
        translate: transform(0 -50%);
        z-index: 10000;
        background-color: #fff;
        cursor: pointer;
      }
      #topoAttrWrap.active {
        right: 0;
        box-shadow: -1px 0px 8px #000069;
      }
    }
  }
}
.svgHeadItemImg {
  background: url("/static/images/icons.png");
  width: 16px;
  height: 16px;
  background-size: 479px 16px;
}

.toolbar-default {
  background-position: -16px 0px;
}
.toolbar-rectangle_selection {
  background-position: -294px 0px;
}
.toolbar-zoomin {
  background-position: -425px 0px;
}
.toolbar-zoomout {
  background-position: -444px 0px;
}
.toolbar-zoomreset {
  background-position: -462px 0px;
}

.infoIcon {
  font-size: 30px;
  position: relative;
  top: 5px;
  left: -4px;
  color: #f7ba2a;
}
#infoWrap {
  padding: 15px;
  text-align: center;
  margin-top: 60px;
}

.removeAttrArrow {
  float: right;
  cursor: pointer;
  font-size: 20px;
  position: relative;
  top: 8px;
  color: #000069;
  transition: all 0.2s;
}
.removeAttrArrow:hover {
  transform: rotate(360deg);
  text-shadow: 2px 2px 1px #000;
}

</style>
